In [1]:
import pandas as pd
import numpy as np
import matplotlib
In [2]:
shotsdf = pd.read_csv('dat.csv')
shotsdf=shotsdf[shotsdf.SHOT_ZONE_BASIC!='Backcourt']
shotsdf=shotsdf[shotsdf.SHOT_ZONE_AREA!='Back court(BC)']
In [3]:
court_shapes = []
outer_lines_shape = dict(
    type='rect',
    xref='x',
    yref='y',
    x0='-250',
    y0='-47.5',
    x1='250',
    y1='422.5',
    line=dict(
        color='rgba(10, 10, 10, 1)',
        width=1
    )
)
court_shapes.append(outer_lines_shape)

hoop_shape = dict(
    type='circle',
    xref='x',
    yref='y',
    x0='7.5',
    y0='7.5',
    x1='-7.5',
    y1='-7.5',
    line=dict(
        color='rgba(10, 10, 10, 1)',
        width=1
    )
)
court_shapes.append(hoop_shape)

backboard_shape = dict(
    type='rect',
    xref='x',
    yref='y',
    x0='-30',
    y0='-7.5',
    x1='30',
    y1='-6.5',
    line=dict(
        color='rgba(10, 10, 10, 1)',
        width=1
    ),
    fillcolor='rgba(10, 10, 10, 1)'
)
court_shapes.append(backboard_shape)

outer_three_sec_shape = dict(
    type='rect',
    xref='x',
    yref='y',
    x0='-80',
    y0='-47.5',
    x1='80',
    y1='143.5',
    line=dict(
        color='rgba(10, 10, 10, 1)',
        width=1
    )
)
court_shapes.append(outer_three_sec_shape)

inner_three_sec_shape = dict(
    type='rect',
    xref='x',
    yref='y',
    x0='-60',
    y0='-47.5',
    x1='60',
    y1='143.5',
    line=dict(
        color='rgba(10, 10, 10, 1)',
        width=1
    )
)
 
court_shapes.append(inner_three_sec_shape)

left_line_shape = dict(
    type='line',
    xref='x',
    yref='y',
    x0='-220',
    y0='-47.5',
    x1='-220',
    y1='92.5',
    line=dict(
        color='rgba(10, 10, 10, 1)',
        width=1
    )
)
 
court_shapes.append(left_line_shape)

right_line_shape = dict(
    type='line',
    xref='x',
    yref='y',
    x0='220',
    y0='-47.5',
    x1='220',
    y1='92.5',
    line=dict(
        color='rgba(10, 10, 10, 1)',
        width=1
    )
)
 
court_shapes.append(right_line_shape)

three_point_arc_shape = dict(
    type='path',
    xref='x',
    yref='y',
    path='M -220 92.5 C -70 300, 70 300, 220 92.5',
    line=dict(
        color='rgba(10, 10, 10, 1)',
        width=1
    )
)
 
court_shapes.append(three_point_arc_shape)

center_circle_shape = dict(
    type='circle',
    xref='x',
    yref='y',
    x0='60',
    y0='482.5',
    x1='-60',
    y1='362.5',
    line=dict(
        color='rgba(10, 10, 10, 1)',
        width=1
    )
)
 
court_shapes.append(center_circle_shape)

res_circle_shape = dict(
    type='circle',
    xref='x',
    yref='y',
    x0='20',
    y0='442.5',
    x1='-20',
    y1='402.5',
    line=dict(
        color='rgba(10, 10, 10, 1)',
        width=1
    )
)
 
court_shapes.append(res_circle_shape)

free_throw_circle_shape = dict(
    type='circle',
    xref='x',
    yref='y',
    x0='60',
    y0='200',
    x1='-60',
    y1='80',
    line=dict(
        color='rgba(10, 10, 10, 1)',
        width=1
    )
)
 
court_shapes.append(free_throw_circle_shape)

res_area_shape = dict(
    type='circle',
    xref='x',
    yref='y',
    x0='40',
    y0='40',
    x1='-40',
    y1='-40',
    line=dict(
        color='rgba(10, 10, 10, 1)',
        width=1,
        dash='dot'
    )
)
 
court_shapes.append(res_area_shape)
In [4]:
def getZoneText(team, zone_name):
    attempted = 0
    #Center Three Pointer
    if (zone_name == "center_3"):
        made = sum(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                & (shotsdf.SHOT_ZONE_AREA == 'Center(C)') 
                                & (shotsdf.SHOT_ZONE_BASIC == 'Above the Break 3')]['SHOT_MADE_FLAG'])
        attempted = len(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                     & (shotsdf.SHOT_ZONE_AREA == 'Center(C)') 
                                     & (shotsdf.SHOT_ZONE_BASIC == 'Above the Break 3')]['SHOT_MADE_FLAG'])
    #Left Center Three Pointer
    elif (zone_name == "left_center_3"):
        made = sum(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                & (shotsdf.SHOT_ZONE_AREA == 'Left Side Center(LC)') 
                                & (shotsdf.SHOT_ZONE_BASIC == 'Above the Break 3')]['SHOT_MADE_FLAG'])
        attempted = len(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                     & (shotsdf.SHOT_ZONE_AREA == 'Left Side Center(LC)') 
                                     & (shotsdf.SHOT_ZONE_BASIC == 'Above the Break 3')]['SHOT_MADE_FLAG'])
    #Right Center Three Pointer
    elif (zone_name == "right_center_3"):
        made = sum(shotsdf[(shotsdf.TEAM_NAME ==team)
                                & (shotsdf.SHOT_ZONE_AREA == 'Right Side Center(RC)') 
                                & (shotsdf.SHOT_ZONE_BASIC == 'Above the Break 3')]['SHOT_MADE_FLAG'])
        attempted = len(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                     & (shotsdf.SHOT_ZONE_AREA == 'Right Side Center(RC)') 
                                     & (shotsdf.SHOT_ZONE_BASIC == 'Above the Break 3')]['SHOT_MADE_FLAG'])
    #Left Corner Three Pointer
    elif (zone_name == "left_corner_3"):
        made = sum(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                & (shotsdf.SHOT_ZONE_BASIC == 'Left Corner 3')]['SHOT_MADE_FLAG'])
        attempted = len(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                     & (shotsdf.SHOT_ZONE_BASIC == 'Left Corner 3')]['SHOT_MADE_FLAG'])
    #Right Corner Three Pointer
    elif (zone_name == "right_corner_3"):    
        made = sum(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                & (shotsdf.SHOT_ZONE_BASIC == 'Right Corner 3')]['SHOT_MADE_FLAG'])
        attempted = len(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                     & (shotsdf.SHOT_ZONE_BASIC == 'Right Corner 3')]['SHOT_MADE_FLAG'])
    #Center Mid Range
    elif (zone_name == "center_mid"):  
        made = sum(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                & (shotsdf.SHOT_ZONE_AREA == 'Center(C)') 
                                & (shotsdf.SHOT_ZONE_BASIC == 'Mid-Range')]['SHOT_MADE_FLAG'])
        attempted = len(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                     & (shotsdf.SHOT_ZONE_AREA == 'Center(C)') 
                                     & (shotsdf.SHOT_ZONE_BASIC == 'Mid-Range')]['SHOT_MADE_FLAG'])
    #Left Center Mid Range
    elif (zone_name == "left_center_mid"):  
        made = sum(shotsdf[((shotsdf.TEAM_NAME ==team) 
                                 & (shotsdf.SHOT_ZONE_AREA == 'Left Side Center(LC)') 
                                 & (shotsdf.SHOT_ZONE_BASIC == 'Mid-Range')) |
                                ((shotsdf.TEAM_NAME ==team) 
                                 & (shotsdf.SHOT_ZONE_AREA == 'Left Side(L)') 
                                 & (shotsdf.SHOT_ZONE_BASIC == 'In The Paint (Non-RA)'))]['SHOT_MADE_FLAG'])
        attempted = len(shotsdf[((shotsdf.TEAM_NAME ==team) 
                                      & (shotsdf.SHOT_ZONE_AREA == 'Left Side Center(LC)') 
                                      & (shotsdf.SHOT_ZONE_BASIC == 'Mid-Range')) |
                                     ((shotsdf.TEAM_NAME ==team) 
                                      & (shotsdf.SHOT_ZONE_AREA == 'Left Side(L)') 
                                      & (shotsdf.SHOT_ZONE_BASIC == 'In The Paint (Non-RA)'))]['SHOT_MADE_FLAG'])
    #Right Center Mid Range
    elif (zone_name == "right_center_mid"):  
        made = sum(shotsdf[((shotsdf.TEAM_NAME ==team) 
                                 & (shotsdf.SHOT_ZONE_AREA == 'Right Side Center(RC)') 
                                 & (shotsdf.SHOT_ZONE_BASIC == 'Mid-Range')) |
                                ((shotsdf.TEAM_NAME ==team) 
                                 & (shotsdf.SHOT_ZONE_AREA == 'Right Side(R)') 
                                 & (shotsdf.SHOT_ZONE_BASIC == 'In The Paint (Non-RA)'))]['SHOT_MADE_FLAG'])
        attempted = len(shotsdf[((shotsdf.TEAM_NAME ==team) 
                                      & (shotsdf.SHOT_ZONE_AREA == 'Right Side Center(RC)') 
                                      & (shotsdf.SHOT_ZONE_BASIC == 'Mid-Range')) |
                                     ((shotsdf.TEAM_NAME ==team) 
                                      & (shotsdf.SHOT_ZONE_AREA == 'Right Side(R)') 
                                      & (shotsdf.SHOT_ZONE_BASIC == 'In The Paint (Non-RA)'))]['SHOT_MADE_FLAG'])
    #Left Mid Range
    elif (zone_name == "left_mid"):  
        made = sum(shotsdf[((shotsdf.TEAM_NAME ==team) 
                                 & (shotsdf.SHOT_ZONE_AREA == 'Left Side(L)') 
                                 & (shotsdf.SHOT_ZONE_BASIC == 'Mid-Range')) |
                                ((shotsdf.TEAM_NAME ==team) 
                                 & (shotsdf.SHOT_ZONE_AREA == 'Left Side(L)') 
                                 & (shotsdf.SHOT_ZONE_BASIC == 'In The Paint (Non-RA)'))]['SHOT_MADE_FLAG'])
        attempted = len(shotsdf[((shotsdf.TEAM_NAME ==team) 
                                      & (shotsdf.SHOT_ZONE_AREA == 'Left Side(L)') 
                                      & (shotsdf.SHOT_ZONE_BASIC == 'Mid-Range')) |
                                     ((shotsdf.TEAM_NAME ==team) 
                                      & (shotsdf.SHOT_ZONE_AREA == 'Left Side(L)') 
                                      & (shotsdf.SHOT_ZONE_BASIC == 'In The Paint (Non-RA)'))]['SHOT_MADE_FLAG'])
    #Right Mid Range
    elif (zone_name == "right_mid"):  
        made = sum(shotsdf[((shotsdf.TEAM_NAME ==team) 
                                 & (shotsdf.SHOT_ZONE_AREA == 'Right Side(R)') 
                                 & (shotsdf.SHOT_ZONE_BASIC == 'Mid-Range')) |
                                ((shotsdf.TEAM_NAME ==team) 
                                 & (shotsdf.SHOT_ZONE_AREA == 'Right Side(R)') 
                                 & (shotsdf.SHOT_ZONE_BASIC == 'In The Paint (Non-RA)'))]['SHOT_MADE_FLAG'])
        attempted = len(shotsdf[((shotsdf.TEAM_NAME ==team) 
                                      & (shotsdf.SHOT_ZONE_AREA == 'Right Side(R)') 
                                      & (shotsdf.SHOT_ZONE_BASIC == 'Mid-Range')) |
                                     ((shotsdf.TEAM_NAME ==team) & 
                                      (shotsdf.SHOT_ZONE_AREA == 'Right Side(R)') 
                                      & (shotsdf.SHOT_ZONE_BASIC == 'In The Paint (Non-RA)'))]['SHOT_MADE_FLAG'])
    #Paint (Not Restricted Area)
    elif (zone_name == "paint"):  
        made = sum(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                & (shotsdf.SHOT_ZONE_AREA == 'Center(C)') 
                                & (shotsdf.SHOT_ZONE_BASIC == 'In The Paint (Non-RA)')]['SHOT_MADE_FLAG'])
        attempted = len(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                     & (shotsdf.SHOT_ZONE_AREA == 'Center(C)') 
                                     & (shotsdf.SHOT_ZONE_BASIC == 'In The Paint (Non-RA)')]['SHOT_MADE_FLAG'])
    #Restricted Area
    elif (zone_name == "restricted_area"):  
        made = sum(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                & (shotsdf.SHOT_ZONE_BASIC == 'Restricted Area')]['SHOT_MADE_FLAG'])
        attempted = len(shotsdf[(shotsdf.TEAM_NAME ==team) 
                                     & (shotsdf.SHOT_ZONE_BASIC == 'Restricted Area')]['SHOT_MADE_FLAG'])
    
    if (attempted == 0): return ''
    else: return "<b>{}-{}<br>{}</b>".format(made, attempted, round(made/attempted, 3)) 
In [5]:
def getZones():
    zone_shapes = []
    #Three Point Line Arc Top Left
    zone1 = dict(
        type='path',
        xref='x',
        yref='y',
        path='M -250,92 L -220,92 C -187,140 -161,174 -80,228  L -95,350 L -250,350 Z',
        line=dict(
            color='rgba(10, 10, 10, 1)',
            width=1
        )
    )
    zone_shapes.append(zone1)
    #Three Point Line Arc Top Right
    zone2 = dict(
        type='path',
        xref='x',
        yref='y',
        path='M 250,92 L 220,92 C 187,140 161,174 80,228  L 95,350 L 250,350 Z',
        line=dict(
            color='rgba(10, 10, 10, 1)',
            width=1
        )
    )

    zone_shapes.append(zone2)
    #Three Point Line Arc Top Center
    zone3 = dict(
        type='path',
        xref='x',
        yref='y',
        path='M -80,228 C -50,240 0,266 80,228 L 95,350 L -95,350 Z',
        line=dict(
            color='rgba(10, 10, 10, 1)',
            width=1
        )
    )
    zone_shapes.append(zone3)
    #Three Point Line Left Corner
    zone4 = dict(
        type='path',
        xref='x',
        yref='y',
        path='M -250,-47.5 L -220,-47.5 L -220,92 L -250,92 Z',
        line=dict(
            color='rgba(10, 10, 10, 1)',
            width=1
        )
    )
    zone_shapes.append(zone4)
    #Three Point Line Right Corner
    zone5 = dict(
        type='path',
        xref='x',
        yref='y',
        path='M 250,-47.5 L 220,-47.5 L 220,92 L 250,92 Z',
        line=dict(
            color='rgba(10, 10, 10, 1)',
            width=1
        )
    )
    zone_shapes.append(zone5)
    #Under the basket
    zone6 = dict(
        type='path',
        xref='x',
        yref='y',
        path='M -40,-47.5 L -40,0 C -40,53 40,53 40,0 L 40,-47.5 Z',
        line=dict(
            color='rgba(10, 10, 10, 1)',
            width=1
        )
    )
    zone_shapes.append(zone6)
    #Paint
    zone7 = dict(    
        type='path',
        xref='x',
        yref='y',
        path='M -60,-47.5 L -40,-47.5 L -40,0 C -40,53 40,53 40,0 L 40,-47.5 L 60,-47.5 L 60,143.5 L -60,143.5 Z',
        line=dict(
            color='rgba(10, 10, 10, 1)',
            width=1
        )
    )

    zone_shapes.append(zone7)
    #Midrange Center
    zone8 = dict(    
        type='path',
        xref='x',
        yref='y',
        path='M -60,143.5 L -80,228 C -50,240 0,266 80,228 L 60,143.5 Z',
        line=dict(
            color='rgba(10, 10, 10, 1)',
            width=1
        )
    )
    zone_shapes.append(zone8)
    #Midrange Center Left
    zone8 = dict(    
        type='path',
        xref='x',
        yref='y',
        path='M -220,92 C -187,140 -161,174 -80,228 L -60,143.5 L -60,92 Z',
        line=dict(
            color='rgba(10, 10, 10, 1)',
            width=1
        )
    )
    zone_shapes.append(zone8)
    #Midrange Center Right
    zone9 = dict(    
        type='path',
        xref='x',
        yref='y',
        path='M 220,92 C 187,140 161,174 80,228 L 60,143.5 L 60,92 Z',
        line=dict(
            color='rgba(10, 10, 10, 1)',
            width=1
        )
    )
    zone_shapes.append(zone9)
    #Midrange Left
    zone10 = dict(    
        type='path',
        xref='x',
        yref='y',
        path='M -220,-47.5 L -220,92 L -60,92 L -60,-47.5 Z',
        line=dict(
            color='rgba(10, 10, 10, 1)',
            width=1
        )
    )
    zone_shapes.append(zone10)
    #Midrange Right
    zone11 = dict(    
        type='path',
        xref='x',
        yref='y',
        path='M 220,-47.5 L 220,92 L 60,92 L 60,-47.5 Z',
        line=dict(
            color='rgba(10, 10, 10, 1)',
            width=1
        )
    )
    zone_shapes.append(zone11)
    return zone_shapes
In [6]:
import plotly.graph_objs as go
from plotly.offline import iplot, init_notebook_mode
init_notebook_mode(connected=True)
def updateVisibility(selectedTeam):
    visibilityValues = []
    for team in sorted(shotsdf.TEAM_NAME.unique()):
        if team == selectedTeam:
            visibilityValues.append(True) 
            visibilityValues.append(True) 
            visibilityValues.append(True) 
        else:
            visibilityValues.append(False)
            visibilityValues.append(False)
            visibilityValues.append(False)
    return visibilityValues

zone_shapes = getZones()
data = []
buttons_data = []
for team in sorted(shotsdf.TEAM_NAME.unique()):
    shot_trace_made = go.Scatter(
        x = shotsdf[(shotsdf['EVENT_TYPE'] == 'Made Shot') & (shotsdf['TEAM_NAME'] == team)]['LOC_X'],
        y = shotsdf[(shotsdf['EVENT_TYPE'] == 'Made Shot') & (shotsdf['TEAM_NAME'] == team)]['LOC_Y'],
        mode = 'markers',
        marker = dict(
            size = 2,
            color = 'rgba(63, 191, 63, 0.9)',
        ), 
        name = 'Made',
        text = shotsdf[(shotsdf['EVENT_TYPE'] == 'Made Shot') & (shotsdf['TEAM_NAME'] == team)]['PLAYER_NAME'],
        textposition = 'top left',
        textfont = dict(
            color = 'rgba(75, 85, 102,0.7)'
        ),
        visible = (team =='Atl')
    )

    shot_trace_missed = go.Scatter(
        x = shotsdf[(shotsdf['EVENT_TYPE'] == 'Missed Shot') & (shotsdf['TEAM_NAME'] == team)]['LOC_X'],
        y = shotsdf[(shotsdf['EVENT_TYPE'] == 'Missed Shot') & (shotsdf['TEAM_NAME'] == team)]['LOC_Y'],
        mode = 'markers',
        marker = dict(
            size = 2,
            color = 'rgba(241, 18, 18, 0.9)',
        ), 
        name = 'Missed',
        text = shotsdf[(shotsdf['EVENT_TYPE'] == 'Missed Shot') & (shotsdf['TEAM_NAME'] == team)]['PLAYER_NAME'],
        textposition = 'top left',
        textfont = dict(
            color = 'rgba(75, 85, 102,0.7)'
        ),
        visible = (team =='Atl')
    )

    shot_zones_text = go.Scatter(
        x=[-175, 0, 175, -120, 0, 120, -190, -135, 0, 0, 135, 190],
        y=[250, 300, 250, 140, 200, 140, -25, 40, 90, -10, 40, -25],
        mode='text',
        name='FG %',
        text=[getZoneText(team, 'left_center_3'), 
              getZoneText(team, 'center_3'), 
              getZoneText(team, 'right_center_3'), 
              getZoneText(team, 'left_center_mid'), 
              getZoneText(team, 'center_mid'), 
              getZoneText(team, 'right_center_mid'), 
              getZoneText(team, 'left_corner_3'), 
              getZoneText(team, 'left_mid'), 
              getZoneText(team, 'paint'), 
              getZoneText(team, 'restricted_area'), 
              getZoneText(team, 'right_mid'), 
              getZoneText(team, 'right_corner_3')],
        textposition='top center',        
        textfont = dict(
            color = 'rgba(10, 10, 10, 1)',
            size = 14
        ),
        visible = (team == 'Atl')
    )
    
    data.append(shot_trace_made)
    data.append(shot_trace_missed)
    data.append(shot_zones_text)
    
    buttons_data.append(
        dict(
            label = team,
            method = 'update',
            args = [{'visible': updateVisibility(team)}, 
                    {'shapes': court_shapes + getZones()}]
        )
    )
    
updatemenus = list([
    dict(active=0,
         buttons = buttons_data,
         direction = 'down',
         pad = {'r': 10, 't': 10},
         showactive = True,
         x = 0.21,
         xanchor = 'left',
         y = 1.19,
         yanchor = 'top',
         font = dict (
             size = 14
         )
    )
])
layout = go.Layout(
    title='Shot Chart for',
    titlefont=dict(
        size=14
    ),
    hovermode = 'closest',
    updatemenus = updatemenus,
    showlegend = True,
    height = 600,
    width = 600, 
    shapes = court_shapes+zone_shapes,
    xaxis = dict(
        showticklabels = False,
        range = [-250, 250]
    ),
    yaxis = dict(
        showticklabels = False,
        range = [-47.5, 452.5]
    )
)
fig = go.Figure(data=data, layout=layout)
iplot(fig)

To use this interactive plot, simply select the team you want to analyze from the drop down menu at the top. Once selected, it will display a scatterplot of the made and missed shots for the entire 2017 NBA season. Hover over each point to see the name of the player that took that shot. In addition, we broke up the area into 12 sections based on location and proximity to the basket. This visual aids in our analysis of how different teams have different shooting patterns. It allows us to directly compare across teams how many shots they took in each area, as well as their shooting percentage. In that sense, we can corroborate our conclusions from the static graphs, in addition to seeing misses and makes. We can confirm that the league is shifting more towards a mix of shooting three pointers and layups. If we cycle through the shot charts for all the teams, we can see that both three pointers and inside shots outnumber midrange shots. Houston as a team stands out in particular in that it takes this philosophy to the extreme. They attempt to maximize their efficiency by only taking close range or three point shots, while there is a dearth of midrange shots. In addition, younger teams such as Philadelphia have a tendency to take and make more shots in the paint, while an older team such as Cleveland will take and make more shots from the outside.

In [7]:
%%html
<script src="https://cdn.rawgit.com/parente/4c3e6936d0d7a46fd071/raw/65b816fb9bdd3c28b4ddf3af602bfd6015486383/code_toggle.js"></script>
In [ ]: